/*
 * fcntl.c
 *
 * Copyright (C) 2019 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "crt_priv.h"
#include <stdlib.h>
#include <string.h>

#define __DONT_DEFINE_OPEN__
#include <fcntl.h>

#define MAX_OPEN_FILES 4096

static handle_t mutex;
static struct
{
    handle_t handle;
    int flags;
    int descriptor_flags;
    off_t position;
    size_t size;
} descriptors[MAX_OPEN_FILES];

handle_t __CRT_PRIVATE(get_raw_handle)(int fd)
{
    syscall_wait_mutex(mutex, NO_TIMEOUT);
    handle_t handle = descriptors[fd].handle;
    syscall_release_mutex(mutex);
    return handle;
}

static inline int __CRT_PRIVATE(open_handle_at)(int fd, handle_t handle, int flags)
{
    object_type_t type;
    sysret_t status = syscall_query_handle(handle, HANDLE_INFO_TYPE, &type, sizeof(type));

    if (status != ERR_SUCCESS || (type != OBJECT_FILE && type != OBJECT_PIPE))
    {
        __CRT_PUBLIC(errno) = EINVAL;
        return -1;
    }

    if (type == OBJECT_FILE)
    {
        qword_t real_size;
        status = syscall_query_file(handle, FILE_INFO_SIZE, &real_size, sizeof(real_size));
        if (status != ERR_SUCCESS)
        {
            __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
            return -1;
        }

        if (real_size > (qword_t)((size_t)-1))
        {
            __CRT_PUBLIC(errno) = EFBIG;
            return -1;
        }

        descriptors[fd].descriptor_flags = 0;
        descriptors[fd].size = (size_t)real_size;
    }
    else
    {
        descriptors[fd].descriptor_flags = FD_PIPE;
        descriptors[fd].size = 0;
    }

    descriptors[fd].handle = handle;
    descriptors[fd].flags = flags;
    descriptors[fd].position = 0;
    return fd;
}

static inline int __CRT_PRIVATE(open_handle)(handle_t handle, int flags)
{
    int fd;
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    for (fd = 0; fd < MAX_OPEN_FILES; fd++)
    {
        if (descriptors[fd].handle == INVALID_HANDLE) break;
    }

    if (fd == MAX_OPEN_FILES)
    {
        __CRT_PUBLIC(errno) = EMFILE;
        return -1;
    }

    fd = __CRT_PRIVATE(open_handle_at)(fd, handle, flags);

    syscall_release_mutex(mutex);
    return fd;
}

int __CRT_PUBLIC(open)(const char *pathname, int flags, mode_t mode)
{
    handle_t handle;
    char fullpath[PATH_MAX];

    if (*pathname == '/')
    {
        if (__CRT_PUBLIC(strlen)(pathname) >= PATH_MAX)
        {
            __CRT_PUBLIC(errno) = ENAMETOOLONG;
            return -1;
        }

        __CRT_PUBLIC(strcpy)(fullpath, pathname);
    }
    else
    {
        __CRT_PUBLIC(getwd)(fullpath);

        if (__CRT_PUBLIC(strlen)(fullpath) + __CRT_PUBLIC(strlen)(pathname) >= PATH_MAX)
        {
            __CRT_PUBLIC(errno) = ENAMETOOLONG;
            return -1;
        }

        __CRT_PUBLIC(strcat)(fullpath, pathname);
    }

    if ((flags & 3) == 3)
    {
        __CRT_PUBLIC(errno) = EINVAL;
        return -1;
    }

    dword_t kernel_flags = FILE_MODE_SHARE_READ | FILE_MODE_SHARE_WRITE;
    dword_t attributes = 0;

    if ((flags & 3) == O_RDONLY) kernel_flags |= FILE_MODE_READ;
    else if ((flags & 3) == O_RDONLY) kernel_flags |= FILE_MODE_WRITE;
    else kernel_flags |= FILE_MODE_READ | FILE_MODE_WRITE;

    if (flags & O_CREAT) kernel_flags |= FILE_MODE_CREATE;
    if (flags & O_TRUNC) kernel_flags |= FILE_MODE_TRUNCATE;

    sysret_t status = syscall_open_file(fullpath, &handle, kernel_flags, attributes);
    if (status != ERR_SUCCESS)
    {
        __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
        return -1;
    }

    int fd = __CRT_PRIVATE(open_handle)(handle, flags);
    if (fd < 0) syscall_close_object(handle);
    return fd;
}

int __CRT_PUBLIC(creat)(const char *pathname, mode_t mode)
{
    return __CRT_PUBLIC(open)(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
}

int __CRT_PUBLIC(close)(int fd)
{
    int ret = -1;
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    if (fd < 0 || fd >= MAX_OPEN_FILES || descriptors[fd].handle == INVALID_HANDLE)
    {
        __CRT_PUBLIC(errno) = EBADF;
        goto cleanup;
    }

    syscall_close_object(descriptors[fd].handle);
    descriptors[fd].handle = INVALID_HANDLE;
    ret = 0;

cleanup:
    syscall_release_mutex(mutex);
    return ret;
}

ssize_t __CRT_PUBLIC(read)(int fd, void *buf, size_t count)
{
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    size_t ret;
    sysret_t status = syscall_read_file(descriptors[fd].handle, buf, descriptors[fd].position, count, &ret);

    descriptors[fd].position += (off_t)ret;

    if (status != ERR_SUCCESS && status != ERR_BEYOND)
    {
        __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
        ret = (off_t)-1;
    }

    syscall_release_mutex(mutex);
    return (ssize_t)ret;
}

ssize_t __CRT_PUBLIC(write)(int fd, const void *buf, size_t count)
{
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    size_t ret;
    sysret_t status = syscall_write_file(descriptors[fd].handle, buf, descriptors[fd].position, count, &ret);

    descriptors[fd].position += (off_t)ret;

    if (status != ERR_SUCCESS && status != ERR_BEYOND)
    {
        __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
        ret = (off_t)-1;
    }

    syscall_release_mutex(mutex);
    return (ssize_t)ret;
}

off_t __CRT_PUBLIC(lseek)(int fd, off_t offset, int whence)
{
    if (whence < SEEK_SET || whence > SEEK_END)
    {
        __CRT_PUBLIC(errno) = EINVAL;
        return (off_t)-1;
    }

    syscall_wait_mutex(mutex, NO_TIMEOUT);

    if (fd < 0 || fd >= MAX_OPEN_FILES || descriptors[fd].handle == INVALID_HANDLE)
    {
        __CRT_PUBLIC(errno) = EBADF;
        goto cleanup;
    }

    if (descriptors[fd].descriptor_flags & FD_PIPE)
    {
        __CRT_PUBLIC(errno) = ESPIPE;
        goto cleanup;
    }

    off_t origin = 0;
    if (whence == SEEK_CUR) origin = descriptors[fd].position;
    else if (whence == SEEK_END) origin = descriptors[fd].size;

    off_t ret = origin + offset;

    if ((offset > 0 && ret < origin) || (offset < 0 && ret > origin))
    {
        __CRT_PUBLIC(errno) = EOVERFLOW;
        goto cleanup;
    }

    descriptors[fd].position = ret;

cleanup:
    syscall_release_mutex(mutex);
    return ret;
}

int __CRT_PUBLIC(dup)(int oldfd)
{
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    handle_t duplicate;
    int flags = descriptors[oldfd].flags;
    sysret_t status = syscall_duplicate_handle(INVALID_HANDLE, descriptors[oldfd].handle, INVALID_HANDLE, &duplicate);
    syscall_release_mutex(mutex);

    if (status != ERR_SUCCESS)
    {
        __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
        return -1;
    }

    return __CRT_PRIVATE(open_handle)(duplicate, flags);
}

int __CRT_PUBLIC(dup2)(int oldfd, int newfd)
{
    int fd = -1;
    syscall_wait_mutex(mutex, NO_TIMEOUT);

    handle_t duplicate;
    sysret_t status = syscall_duplicate_handle(INVALID_HANDLE, descriptors[oldfd].handle, INVALID_HANDLE, &duplicate);

    if (status != ERR_SUCCESS)
    {
        __CRT_PUBLIC(errno) = __CRT_PRIVATE(translate_error)(status);
        goto cleanup;
    }

    fd = __CRT_PRIVATE(open_handle_at)(newfd, duplicate, descriptors[oldfd].flags);

cleanup:
    syscall_release_mutex(mutex);
    return fd;
}

int __CRT_PRIVATE(initialize_files)(process_params_t *params)
{
    sysret_t ret = syscall_create_mutex(NULL, TRUE, &mutex);
    if (ret != ERR_SUCCESS) return -1;

    int i;
    for (i = 0; i < MAX_OPEN_FILES; i++) descriptors[i].handle = INVALID_HANDLE;

    if (params->standard_input != INVALID_HANDLE) __CRT_PRIVATE(open_handle_at)(STDIN_FILENO, params->standard_input, O_RDONLY);
    if (params->standard_input != INVALID_HANDLE) __CRT_PRIVATE(open_handle_at)(STDOUT_FILENO, params->standard_output, O_WRONLY);
    if (params->standard_input != INVALID_HANDLE) __CRT_PRIVATE(open_handle_at)(STDERR_FILENO, params->standard_error, O_WRONLY);
    return 0;
}
